/*
EP3D is a real-time 3D planet engine , which in addition to providing 
substandard scene rendering and scene management, of course, it also 
provides some basic class libraries to build the entire virtual 
planet, or even the entire universe.

Copyright (C) 2010  Hongjiang Zhang	(zhjwyat@gmail.com)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "EP3DMemory.h"
#include "EP3DLogger.h"

#ifdef EP3DCORE_MEM_MANAGE
namespace EP3D
{
	size_t Memory::ms_uiNumNewCalls = 0;
	size_t Memory::ms_uiNumDeleteCalls = 0;
	size_t Memory::ms_uiMaxAllowedBytes = 0;
	size_t Memory::ms_uiNumBlocks = 0;
	size_t Memory::ms_uiNumBytes = 0;
	Memory::Block* Memory::ms_pHead = 0;
	Memory::Block* Memory::ms_pTail = 0;
	bool Memory::ms_bTrackSize = false;
	size_t Memory::ms_uiMaxAllocatedBytes = 0;
	size_t Memory::ms_uiMaxBlockSize = 0;
	CriticalSection Memory::ms_kLock;
	size_t Memory::ms_auiHistogram[32] =
	{
		0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0
	};

	void* Memory::Allocate(Alloctor* allocator, size_t uiSize, const char* acFile, uint uiLine, const char* acFunction, bool bIsArray)
	{
		Scoped s1(&ms_kLock);

		ms_uiNumNewCalls++;

		LogAssert(uiSize > 0);

		size_t uiExtendedSize = sizeof(Block) + uiSize;
		char* pcAddr = (char*)allocator->Allocate(uiExtendedSize);
		if (pcAddr == NULL)
		{
			LogFatal("new:out of memory when allocating:%u bytes", uiSize);
			LogAssert(pcAddr);
		}

		Block* pBlock = (Block*)pcAddr;
		pBlock->Size = uiSize;
		pBlock->File = acFile;
		pBlock->Line = uiLine;
		pBlock->Function = acFunction;
		pBlock->IsArray = bIsArray;

		InsertBlock(pBlock);

		pcAddr += sizeof(Block);
		
		ms_uiNumBlocks++;
		ms_uiNumBytes +=uiSize;

		if (ms_uiMaxAllowedBytes > 0 && ms_uiNumBytes > ms_uiMaxAllowedBytes)
		{
			LogAssert(false);
		}

		if (ms_uiNumBytes > ms_uiMaxAllocatedBytes)
		{
			ms_uiMaxAllocatedBytes = ms_uiNumBytes;
		}

		if (ms_bTrackSize)
		{
			if (uiSize > ms_uiMaxBlockSize)
			{
				ms_uiMaxBlockSize = uiSize;
			}

			uint uiTwoPowerI = 1;
			int i;
			for (i = 0; i <= 30; i++, uiTwoPowerI <<= 1)
			{
				if (uiSize <= uiTwoPowerI)
				{
					ms_auiHistogram[i]++;
					break;
				}
			}
			if (i == 31)
			{
				ms_auiHistogram[i]++;
			}
		}
		return (void*)pcAddr;
	}

	void Memory::Deallocate(Alloctor* allocator, char *pvAddr, bool bIsArray)
	{
		Scoped s1(&ms_kLock);

		ms_uiNumDeleteCalls++;

		if (!pvAddr)
		{
			return;
		}

		pvAddr -= sizeof(Block);

		Block* pBlock = (Block*)pvAddr;
		RemoveBlock(pBlock);

		LogAssert(pBlock->IsArray == bIsArray);

		LogAssert(ms_uiNumBlocks > 0 && ms_uiNumBytes >= pBlock->Size);
		ms_uiNumBlocks--;
		ms_uiNumBytes -= pBlock->Size;

		allocator->Deallocate(pvAddr, pBlock->Size);
	}

	void Memory::InsertBlock(Memory::Block *pBlock)
	{
		if (ms_pTail)
		{
			pBlock->Prev = ms_pTail;
			pBlock->Next = 0;
			ms_pTail->Next = pBlock;
			ms_pTail = pBlock;
		}
		else
		{
			pBlock->Prev = 0;
			pBlock->Next = 0;
			ms_pHead = pBlock;
			ms_pTail = pBlock;
		}
	}

	void Memory::RemoveBlock(Memory::Block* pBlock)
	{
		if (pBlock->Prev)
		{
			pBlock->Prev->Next = pBlock->Next;
		}
		else
		{
			ms_pHead = pBlock->Next;
		}

		if (pBlock->Next)
		{
			pBlock->Next->Prev = pBlock->Prev;
		}
		else
		{
			ms_pTail = pBlock->Prev;
		}
	}

	void Memory::ReportToFile(const char* acFilename)
	{
		Scoped s1(&ms_kLock);
#pragma warning (push)
#pragma warning (disable : 4996)
		FILE* pfLog = fopen(acFilename, "wt");
#pragma warning (pop)
		if (NULL == pfLog)
		{
			LogError(TEXT("Memory Log File Open Failed!"));
			return;
		}
		fseek(pfLog, 0, SEEK_SET);

		char buffer[256];
		sprintf_s(buffer, 256, "************************* Memory Information *************************\n");
		fputs(buffer, pfLog);

		sprintf_s(buffer, 256, "Total number of 'new' calls = %d\n", 
			ms_uiNumNewCalls);
		fputs(buffer, pfLog);

		sprintf_s(buffer, 256, "Total number of 'delete' calls = %d\n", 
			ms_uiNumDeleteCalls);
		fputs(buffer, pfLog);

		sprintf_s(buffer, 256, "Maximun number of allocated bytes = %d\n\n", 
			ms_uiMaxAllocatedBytes);
		fputs(buffer, pfLog);

		sprintf_s(buffer, 256, "Remaining number of block = %d\n", 
			ms_uiNumBlocks);
		fputs(buffer, pfLog);

		sprintf_s(buffer, 256, "Remaining number of bytes = %d\n\n", 
			ms_uiNumBytes);
		fputs(buffer, pfLog);

		size_t uiNumKnownBlocks = 0;
		size_t uiNumKnownBytes = 0;
		size_t uiNumUnknownBlocks = 0;
		size_t uiNumUnknownBytes = 0;
		Block* pBlock = ms_pHead;
		while (pBlock)
		{
			if (pBlock->File)
			{
				uiNumKnownBlocks++;
				uiNumKnownBytes += pBlock->Size;
			}
			else
			{
				uiNumUnknownBlocks++;
				uiNumUnknownBytes += pBlock->Size;
			}
			pBlock = pBlock->Next;
		}

		sprintf_s(buffer, 256, "Remaining number of known blocks = %d\n", 
			uiNumKnownBlocks);
		fputs(buffer, pfLog);

		sprintf_s(buffer, 256, "Remaining number of known bytes = %d\n\n", 
			uiNumKnownBytes);
		fputs(buffer, pfLog);

		sprintf_s(buffer, 256, "Remaining number of unknown blocks = %d\n", 
			uiNumUnknownBlocks);
		fputs(buffer, pfLog);

		sprintf_s(buffer, 256, "Remaining number of unknown bytes = %d\n\n", 
			uiNumUnknownBytes);
		fputs(buffer, pfLog);

		pBlock = ms_pHead;
		size_t uiIndex = 0;
		while (pBlock)
		{

			sprintf_s(buffer, 256, "block = %d\n", 
				uiIndex);
			fputs(buffer, pfLog);

			sprintf_s(buffer, 256, "size = %d\n", 
				pBlock->Size);
			fputs(buffer, pfLog);

			if (pBlock->File)
			{
				sprintf_s(buffer, 256, "file = %s\n", 
					pBlock->File);
				fputs(buffer, pfLog);

				sprintf_s(buffer, 256, "line = %d\n", 
					pBlock->Line);
				fputs(buffer, pfLog);

				sprintf_s(buffer, 256, "function = %s\n", 
					pBlock->Function);
				fputs(buffer, pfLog);
			}
			else
			{
				sprintf_s(buffer, 256, "file = unknown\n");
				fputs(buffer, pfLog);

				sprintf_s(buffer, 256, "line = unknown\n");
				fputs(buffer, pfLog);

				sprintf_s(buffer, 256, "function = unknown\n");
				fputs(buffer, pfLog);
			}

			sprintf_s(buffer, 256, "array = %d\n\n", (int)pBlock->IsArray);
			fputs(buffer, pfLog);

			pBlock = pBlock->Next;
			uiIndex++;
		}

		sprintf_s(buffer, 256, "**********************************************************************\n");
		fputs(buffer, pfLog);

		fclose(pfLog);
	}

	void Memory::ReportToDebugger()
	{	
		Scoped s1(&ms_kLock);

		char buffer[256];
		sprintf_s(buffer, 256, "\n************************* Memory Information *************************\n");
		OutputDebugString(buffer);

		sprintf_s(buffer, 256, "Total number of 'new' calls = %d\n", 
			ms_uiNumNewCalls);
		OutputDebugString(buffer);

		sprintf_s(buffer, 256, "Total number of 'delete' calls = %d\n", 
			ms_uiNumDeleteCalls);
		OutputDebugString(buffer);

		sprintf_s(buffer, 256, "Maximun number of allocated bytes = %d\n\n", 
			ms_uiMaxAllocatedBytes);
		OutputDebugString(buffer);

		sprintf_s(buffer, 256, "Remaining number of block = %d\n", 
			ms_uiNumBlocks);
		OutputDebugString(buffer);

		sprintf_s(buffer, 256, "Remaining number of bytes = %d\n\n", 
			ms_uiNumBytes);
		OutputDebugString(buffer);

		size_t uiNumKnownBlocks = 0;
		size_t uiNumKnownBytes = 0;
		size_t uiNumUnknownBlocks = 0;
		size_t uiNumUnknownBytes = 0;
		Block* pBlock = ms_pHead;
		while (pBlock)
		{
			if (pBlock->File)
			{
				uiNumKnownBlocks++;
				uiNumKnownBytes += pBlock->Size;
			}
			else
			{
				uiNumUnknownBlocks++;
				uiNumUnknownBytes += pBlock->Size;
			}
			pBlock = pBlock->Next;
		}

		sprintf_s(buffer, 256, "Remaining number of known blocks = %d\n", 
			uiNumKnownBlocks);
		OutputDebugString(buffer);

		sprintf_s(buffer, 256, "Remaining number of known bytes = %d\n\n", 
			uiNumKnownBytes);
		OutputDebugString(buffer);

		sprintf_s(buffer, 256, "Remaining number of unknown blocks = %d\n", 
			uiNumUnknownBlocks);
		OutputDebugString(buffer);

		sprintf_s(buffer, 256, "Remaining number of unknown bytes = %d\n\n", 
			uiNumUnknownBytes);
		OutputDebugString(buffer);
		
		pBlock = ms_pHead;
		size_t uiIndex = 0;
		while (pBlock)
		{
			sprintf_s(buffer, 256, "block = %d\n", 
				uiIndex);
			OutputDebugString(buffer);

			sprintf_s(buffer, 256, "size = %d\n", 
				pBlock->Size);
			OutputDebugString(buffer);

			if (pBlock->File)
			{
				sprintf_s(buffer, 256, "file = %s\n", 
					pBlock->File);
				OutputDebugString(buffer);

				sprintf_s(buffer, 256, "line = %d\n", 
					pBlock->Line);
				OutputDebugString(buffer);

				sprintf_s(buffer, 256, "function = %s\n", 
					pBlock->Function);
				OutputDebugString(buffer);
			}
			else
			{
				sprintf_s(buffer, 256, "file = unknown\n");
				OutputDebugString(buffer);

				sprintf_s(buffer, 256, "line = unknown\n");
				OutputDebugString(buffer);

				sprintf_s(buffer, 256, "function = unknown\n");
				OutputDebugString(buffer);
			}

			sprintf_s(buffer, 256, "array = %d\n\n", (int)pBlock->IsArray);
			OutputDebugString(buffer);

			pBlock = pBlock->Next;
			uiIndex++;
		}

		sprintf_s(buffer, 256, "**********************************************************************\n");
		OutputDebugString(buffer);
	}

}//end namespace

void* _EP3DMalloc(size_t uiSize, const char* acFile, unsigned int uiLine, const char* acFunction)
{
	return EP3D::Memory::Allocate(EP3D::StandardAllocator::GetInstance(), uiSize, acFile, uiLine, acFunction, false);
}

void _EP3DFree(void* pvAddr)
{
	return EP3D::Memory::Deallocate(EP3D::StandardAllocator::GetInstance(), (char*)pvAddr, false);
}

#endif